(function(exports) {

	/*
	 * tile.stamen.js v1.3.0
	 */

	var SUBDOMAINS = "a. b. c. d.".split(" "),
		MAKE_PROVIDER = function(layer, type, minZoom, maxZoom) {
			return {
				"url": ["http://{S}tile.stamen.com/", layer, "/{Z}/{X}/{Y}.", type].join(""),
				"type": type,
				"subdomains": SUBDOMAINS.slice(),
				"minZoom": minZoom,
				"maxZoom": maxZoom,
				"attribution": [
					'Map tiles by <a href="http://stamen.com/">Stamen Design</a>, ',
					'Data by <a href="http://www.cnblogs.com/shanligang/">浙江农林大学空间信息技术协会</a>, ',
					'作者 <a href="http://www.cnblogs.com/shanligang/">单立刚</a>.'
				].join("")
			};
		},
		PROVIDERS = {
			"toner": MAKE_PROVIDER("toner", "png", 0, 20),
			"terrain": MAKE_PROVIDER("terrain", "jpg", 4, 18),
			"watercolor": MAKE_PROVIDER("watercolor", "jpg", 1, 18),
			"trees-cabs-crime": {
				"url": "http://{S}.tiles.mapbox.com/v3/stamen.trees-cabs-crime/{Z}/{X}/{Y}.png",
				"type": "png",
				"subdomains": "a b c d".split(" "),
				"minZoom": 11,
				"maxZoom": 18,
				"extent": [{
					"lat": 37.853,
					"lon": -122.577
				}, {
					"lat": 37.684,
					"lon": -122.313
				}],
				"attribution": [
					'Map tiles by <a href="http://stamen.com/">Stamen Design</a>, ',
					'Data by <a href="http://www.cnblogs.com/shanligang/">浙江农林大学空间信息协会</a>, ',
					'作者 <a href="http://www.cnblogs.com/shanligang/">单立刚</a>.'
				].join(" ")
			}
		};

	// set up toner and terrain flavors
	setupFlavors("toner", ["hybrid", "labels", "lines", "background", "lite"]);
	setupFlavors("terrain", ["background"]);
	setupFlavors("terrain", ["labels", "lines"], "png");

	// toner 2010
	deprecate("toner", ["2010"]);

	// toner 2011 flavors
	deprecate("toner", ["2011", "2011-lines", "2011-labels", "2011-lite"]);

	var odbl = [
		"toner",
		"toner-hybrid",
		"toner-labels",
		"toner-lines",
		"toner-background",
		"toner-lite"
	];

	for (var i = 0; i < odbl.length; i++) {
		var key = odbl[i];

		PROVIDERS[key].retina = true;
		PROVIDERS[key].attribution = [
			'Map tiles by <a href="http://stamen.com/">Stamen Design</a>, ',
			'Data by <a href="http://www.cnblogs.com/shanligang/">浙江农林大学空间信息协会</a>, ',
			'作者 <a href="http://www.cnblogs.com/shanligang/">单立刚</a>.'
		].join("");
	}

	/*
	 * Export stamen.tile to the provided namespace.
	 */
	exports.stamen = exports.stamen || {};
	exports.stamen.tile = exports.stamen.tile || {};
	exports.stamen.tile.providers = PROVIDERS;
	exports.stamen.tile.getProvider = getProvider;

	function deprecate(base, flavors) {
		var provider = getProvider(base);

		for (var i = 0; i < flavors.length; i++) {
			var flavor = [base, flavors[i]].join("-");
			PROVIDERS[flavor] = MAKE_PROVIDER(flavor, provider.type, provider.minZoom, provider.maxZoom);
			PROVIDERS[flavor].deprecated = true;
		}
	};

	/*
	 * A shortcut for specifying "flavors" of a style, which are assumed to have the
	 * same type and zoom range.
	 */
	function setupFlavors(base, flavors, type) {
		var provider = getProvider(base);
		for (var i = 0; i < flavors.length; i++) {
			var flavor = [base, flavors[i]].join("-");
			PROVIDERS[flavor] = MAKE_PROVIDER(flavor, type || provider.type, provider.minZoom, provider.maxZoom);
		}
	}

	/*
	 * Get the named provider, or throw an exception if it doesn't exist.
	 */
	function getProvider(name) {
		if (name in PROVIDERS) {
			var provider = PROVIDERS[name];

			if (provider.deprecated && console && console.warn) {
				console.warn(name + " is a deprecated style; it will be redirected to its replacement. For performance improvements, please change your reference.");
			}

			return provider;
		} else {
			throw 'No such provider (' + name + ')';
		}
	}

	/*
	 * StamenTileLayer for modestmaps-js
	 * <https://github.com/modestmaps/modestmaps-js/>
	 *
	 * Works with both 1.x and 2.x by checking for the existence of MM.Template.
	 */
	if (typeof MM === "object") {
		var ModestTemplate = (typeof MM.Template === "function") ? MM.Template : MM.TemplatedMapProvider;
		MM.StamenTileLayer = function(name) {
			var provider = getProvider(name);
			this._provider = provider;
			MM.Layer.call(this, new ModestTemplate(provider.url, provider.subdomains));
			this.provider.setZoomRange(provider.minZoom, provider.maxZoom);
			this.attribution = provider.attribution;
		};

		MM.StamenTileLayer.prototype = {
			setCoordLimits: function(map) {
				var provider = this._provider;
				if (provider.extent) {
					map.coordLimits = [
						map.locationCoordinate(provider.extent[0]).zoomTo(provider.minZoom),
						map.locationCoordinate(provider.extent[1]).zoomTo(provider.maxZoom)
					];
					return true;
				} else {
					return false;
				}
			}
		};

		MM.extend(MM.StamenTileLayer, MM.Layer);
	}

	/*
	 * StamenTileLayer for Leaflet
	 * <http://leaflet.cloudmade.com/>
	 *
	 * Tested with version 0.3 and 0.4, but should work on all 0.x releases.
	 */
	if (typeof L === "object") {
		L.StamenTileLayer = L.TileLayer.extend({
			initialize: function(name, options) {
				var provider = getProvider(name),
					url = provider.url.replace(/({[A-Z]})/g, function(s) {
						return s.toLowerCase();
					}),
					opts = L.Util.extend({}, options, {
						"minZoom": provider.minZoom,
						"maxZoom": provider.maxZoom,
						"subdomains": provider.subdomains,
						"scheme": "xyz",
						"attribution": provider.attribution
					});
				L.TileLayer.prototype.initialize.call(this, url, opts);
			}
		});

		/*
		 * Factory function for consistency with Leaflet conventions
		 */
		L.stamenTileLayer = function(options, source) {
			return new L.StamenTileLayer(options, source);
		};
	}

	/*
	 * StamenTileLayer for OpenLayers
	 * <http://openlayers.org/>
	 *
	 * Tested with v2.1x.
	 */
	if (typeof OpenLayers === "object") {
		// make a tile URL template OpenLayers-compatible
		function openlayerize(url) {
			return url.replace(/({.})/g, function(v) {
				return "$" + v.toLowerCase();
			});
		}

		// based on http://www.bostongis.com/PrinterFriendly.aspx?content_name=using_custom_osm_tiles
		OpenLayers.Layer.Stamen = OpenLayers.Class(OpenLayers.Layer.OSM, {
			initialize: function(name, options) {
				var provider = getProvider(name),
					url = provider.url,
					subdomains = provider.subdomains,
					hosts = [];
				if (url.indexOf("{S}") > -1) {
					for (var i = 0; i < subdomains.length; i++) {
						hosts.push(openlayerize(url.replace("{S}", subdomains[i])));
					}
				} else {
					hosts.push(openlayerize(url));
				}
				options = OpenLayers.Util.extend({
					"numZoomLevels": provider.maxZoom,
					"buffer": 0,
					"transitionEffect": "resize",
					// see: <http://dev.openlayers.org/apidocs/files/OpenLayers/Layer/OSM-js.html#OpenLayers.Layer.OSM.tileOptions>
					// and: <http://dev.openlayers.org/apidocs/files/OpenLayers/Tile/Image-js.html#OpenLayers.Tile.Image.crossOriginKeyword>
					"tileOptions": {
						"crossOriginKeyword": null
					},
					"attribution": provider.attribution
				}, options);
				return OpenLayers.Layer.OSM.prototype.initialize.call(this, name, hosts, options);
			}
		});
	}

	/*
	 * StamenMapType for Google Maps API V3
	 * <https://developers.google.com/maps/documentation/javascript/>
	 */
	if (typeof google === "object" && typeof google.maps === "object") {
		google.maps.StamenMapType = function(name) {
			var provider = getProvider(name),
				subdomains = provider.subdomains;
			return google.maps.ImageMapType.call(this, {
				"getTileUrl": function(coord, zoom) {
					var numTiles = 1 << zoom,
						wx = coord.x % numTiles,
						x = (wx < 0) ? wx + numTiles : wx,
						y = coord.y,
						index = (zoom + x + y) % subdomains.length;
					return provider.url
						.replace("{S}", subdomains[index])
						.replace("{Z}", zoom)
						.replace("{X}", x)
						.replace("{Y}", y);
				},
				"tileSize": new google.maps.Size(256, 256),
				"name": name,
				"minZoom": provider.minZoom,
				"maxZoom": provider.maxZoom
			});
		};
		// FIXME: is there a better way to extend classes in Google land?
		google.maps.StamenMapType.prototype = new google.maps.ImageMapType("_");
	}

})(typeof exports === "undefined" ? this : exports);